package lossy;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.imageio.ImageIO;

import dcc.CompressionAlgorithm;
import dcc.Rle;

/**
 * The Class Lossy.
 */
public class Lossy {

	/** The Constant N - size of blocks. */
	private static final int N = 8;

	/** The height of image. */
	private static int height;

	/** The width. of image */
	private static int width;

	/** The Constant FILE_TO_READ. */
	private static String FILE_TO_READ = "testdata//dcc2//lena.bmp";

	/** The Constant COMPRESSED_FILE. */
	private static String COMPRESSED_FILE = "testdata//dcc2//compressed.bin";

	/** The Constant DECOMPRESSED_FILE. */
	private static String DECOMPRESSED_FILE = "testdata//dcc2//decompressed.bmp";
	private final static String DECOMPRESSED_FILE_BIN = DECOMPRESSED_FILE
			+ ".bin";

	private static int THRESHOLD_VALUE = 10;
	private static final int CODE_WORD_LENGTH = 12;

	// private static List<Block> blocks = divideIntoBlocks(FILE_TO_READ);

	/**
	 * The main method: runs compression and/or decompression
	 * 
	 * @param args
	 *            the arguments
	 */
	public static void main(String[] args) {
		if (args.length > 0) {
			FILE_TO_READ = args[0];
			THRESHOLD_VALUE = Integer.parseInt(args[1]);
			COMPRESSED_FILE = "testdata//dcc2//" + args[2] + ".bin";
			DECOMPRESSED_FILE = "testdata//dcc2//" + args[3] + "_decompressed.bmp";
		}
		DCT dct = new DCT();
		compress(dct);
		String res="";
		try{
			Rle ca = new Rle();
			ca.compress(new BufferedReader(new FileReader(DECOMPRESSED_FILE_BIN)), 
	                        new BufferedWriter(new FileWriter(COMPRESSED_FILE)));
			res += ca.decompress(new BufferedReader(new FileReader(COMPRESSED_FILE)));
		} catch(Exception e){
			System.out.println("rle fails "+e.getMessage());
		}
		decompress(dct, res);
		 calculatePSNR();
		System.out.println("exe'd");
	}

	/**
	 * Compression method: - divides image into blocks 8x8 - performs DCT -
	 * performs Treshold - TODO: compute zero effectively - saves file
	 * 
	 * @param dct
	 */
	public static void compress(DCT dct) {

		List<Block> blocks = divideIntoBlocks(FILE_TO_READ);
		for (Block block : blocks) {
			double[][] out = block.getBlock();
			out = dct.performDCT(block.getBlock());
			out = Threshold.performThreshold(out, N, THRESHOLD_VALUE);
			block.setBlock(out);

		}
		saveCompressedFile(blocks, DECOMPRESSED_FILE_BIN);
	}
	
	/**
	 * Decompression method: - divides image into blocks 8x8 - performs iDCT - -
	 * saves file
	 * 
	 * @param dct
	 */
	public static void decompress(DCT dct) {

		List<Block> blocks = divideIntoBlocksCompressedFile(DECOMPRESSED_FILE_BIN);
		for (Block block : blocks) {
			double[][] out = block.getBlock();
			out = dct.performIDCT(block.getBlock());
			block.setBlock(out);

		}
		saveFile(blocks, DECOMPRESSED_FILE, "bmp");
	}

	public static void decompress(DCT dct,String res) {

		List<Block> blocks = divideIntoBlocksDataString(res);
		for (Block block : blocks) {
			double[][] out = block.getBlock();
			out = dct.performIDCT(block.getBlock());
			block.setBlock(out);

		}
		saveFile(blocks, DECOMPRESSED_FILE, "bmp");
	}
	
	private static void calculatePSNR() {
		try {
			FileInputStream in = new FileInputStream(FILE_TO_READ);
			FileInputStream out = new FileInputStream(DECOMPRESSED_FILE);

			long size = 0;
			int a = 0;
			int b = 0;
			int maxa = 0;
			double mse = 0;
			while (((a = in.read()) != -1) && ((b = out.read()) != -1)) {
				size++;
				if (a > maxa) {
					maxa = a;
				}
				mse += Math.pow((a - b), 2);
			}
			System.out.println("MSE: " + mse);
			mse = mse / (double) size;
			System.out.println("MSE: " + mse);
			double psnr = 20 * Math.log10(((double) maxa / (double) Math
					.sqrt(mse)));
			System.out.println("PSNR: " + psnr);
		} catch (Exception e) {
			System.out.println("Error: " + e.getMessage());
		}
	}

	/**
	 * Divide into 8x8 blocks.
	 * 
	 * @param filename
	 *            the filename
	 * @return the list
	 */
	public static List<Block> divideIntoBlocks(String filename) {
		List<Block> blocks = new ArrayList<Block>();
		double[][] block;
		int a = 0;
		int b = 0;

		try {
			BufferedImage image = ImageIO.read(new FileInputStream(filename));
			height = image.getHeight();
			width = image.getWidth();

			for (int i = 0; i < image.getWidth(); i += N) {
				for (int j = 0; j < image.getHeight(); j += N) {

					b = 0;
					block = new double[N][N];
					for (int ii = i; ii < (i + N); ii++) {
						a = 0;
						for (int jj = j; jj < (j + N); jj++) {
							block[b][a] = DCT.getRed(image.getRGB(ii, jj));
							a++;
						}
						b++;
					}
					blocks.add(new Block(block));
				}
			}

		} catch (Exception e) {
			System.out.println("error during IO - " + e.toString());
		}
		return blocks;
	}

	public static List<Block> divideIntoBlocksCompressedFile(String filename) {
		List<Block> blocks = new ArrayList<Block>();
		double[][] block;
		int a = 0;
		int b = 0;

		BufferedReader in = null;
		try {
			in = new BufferedReader(new FileReader(filename));
//			InputStream in = new FileInputStream(new File(filename));
//			InputStream inS = new FileInputStream(new File(filename));
//			lzw.Input in = new lzw.Input(inS, CODE_WORD_LENGTH);
			byte[] k = new byte[1];
//			width = in.read();
//			height = in.read();
			String inString = in.readLine();
			String[] inp = inString.split(",");
			int counter=0;
			int input;
//			while((input = in.read(k,0,1)) == 1){
				for (int i = 0; i < width; i += N) {
					for (int j = 0; j < height; j += N) {
	
						b = 0;
						block = new double[N][N];
						for (int ii = i; ii < (i + N); ii++) {
							a = 0;
							for (int jj = j; jj < (j + N); jj++) {
//								input = in.read();
								input = Integer.parseInt(inp[counter]);counter++;
								if(input == -1){in.close(); throw new Exception("the end is near");}
//								if(((int)(Math.random()*1767)) == 23){
//									System.out.println("in = "+input);
//								}
								block[b][a] = input;
								a++;
							}
							b++;
						}
						blocks.add(new Block(block));
//						System.out.println("block added: "+blocks.size());
					}
				}
//			}
				
			in.close();

		} catch (Exception e) {
			System.out.println("error during IO - " + e.toString());
                        e.printStackTrace();
		}
		finally {
			try{
			in.close();
			} catch(Exception e){}
		}
		return blocks;
	}


	public static List<Block> divideIntoBlocksDataString(String res) {
		List<Block> blocks = new ArrayList<Block>();
		double[][] block;
		int a = 0;
		int b = 0;

		try {
			String[] inp = res.split(",");
			int counter=0;
			sth:
				for (int i = 0; i < width; i += N) {
					for (int j = 0; j < height; j += N) {
	
						b = 0;
						block = new double[N][N];
						for (int ii = i; ii < (i + N); ii++) {
							a = 0;
							for (int jj = j; jj < (j + N); jj++) {
								if(counter == inp.length || inp[counter].isEmpty()){
									break sth;
								} 
						
								block[b][a] = Integer.parseInt(inp[counter]);
								counter++;
								a++;
							}
							b++;
						}
						blocks.add(new Block(block));
					}
				}

		} catch (Exception e) {
			System.out.println("error during IO - " + e.getMessage());
                        e.printStackTrace();
		}
		return blocks;
	}
	
	private static void saveCompressedFile(List<Block> blocks, String filename) {
		try {
			double[][] block;
			int index = 0;

			BufferedWriter out = new BufferedWriter(new FileWriter(filename));
			for (int i = 0; i < width; i += N) {
				for (int j = 0; j < height; j += N) {
					block = blocks.get(index).getBlock();
					for (int ii = 0; ii < block.length; ii++) {
						for (int jj = 0; jj < block[ii].length; jj++) {
							int nextRes = 666;
							if(jj + 1 < block[ii].length){nextRes = (int) block[ii][jj+1];}
							else if(ii+1 < block.length){nextRes = (int) block[ii+1][0];}
							if(((int) block[ii][jj]) != 0 || nextRes != 0){
								out.write(Integer.toString((int) block[ii][jj])+",");
							} else {
								out.write(Integer.toString((int) block[ii][jj]));
							}
						}
					}
					index++;
				}
				out.newLine();
			}
			out.flush();
			out.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Saves file.
	 * 
	 * @param blocks
	 *            the blocks
	 * @param filename
	 *            the filename
	 */
	private static void saveFile(List<Block> blocks, String filename,
			String extension) {
		try {
			BufferedImage image = new BufferedImage(width, height,
					BufferedImage.TYPE_BYTE_GRAY);
			double[][] block;
			int index = 0;

			for (int i = 0; i < image.getWidth(); i += N) {
				for (int j = 0; j < image.getHeight(); j += N) {
//if(index == blocks.size()){ImageIO.write(image, extension, new FileOutputStream(filename)); return;}
					block = blocks.get(index).getBlock();
					for (int ii = 0; ii < block.length; ii++) {
						for (int jj = 0; jj < block[ii].length; jj++) {
							int res = DCT.mixColor((int) block[ii][jj],
									(int) block[ii][jj], (int) block[ii][jj]);
							if (DCT.getRed(res) > 255) {
//								System.out.println(res + " " + DCT.getRed(res));
								res = DCT.mixColor(255, 255, 255);
							} else if (DCT.getRed(res) < 0) {
//								System.out.println(res + " " + DCT.getRed(res));
								res = DCT.mixColor(0, 0, 0);
							}
							image.setRGB(i + ii, j + jj, res);
						}
					}
					index++;
				}
			}

			ImageIO.write(image, extension, new FileOutputStream(filename));

		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}
